//
//  TextTag.swift
//  LibUI
//
//  Created by ChangtanSun on 2017/6/1.
//  Copyright © 2017年 ChangtanSun. All rights reserved.
//

import UIKit

public class TextTag: NSObject, TextAttachmentContainer, NSCoding, NSCopying {
    public var lineStyle: NSUnderlineStyle = NSUnderlineStyle.single
    public var strokeWidth: CGFloat = 1
    public var strokeColor: UIColor
    public var lineJoin: CGLineJoin = .miter
    public var padding: UIEdgeInsets?
    public var margin: UIEdgeInsets?
    public var cornerRadius: CGFloat?
    public var shadow: NSShadow?
    public var fillColor: UIColor?
    public var text: String?
    public var attributedText: NSMutableAttributedString?
    public var corners: UIRectCorner?
    public var cornerRadii: CGSize?

    public init(strokeColor: UIColor) {
        self.strokeColor = strokeColor

        super.init()
    }

    public required init?(coder aDecoder: NSCoder) {
        self.strokeColor = aDecoder.decodeObject(forKey: "strokeColor") as! UIColor

        self.lineStyle = NSUnderlineStyle(rawValue: aDecoder.decodeInteger(forKey: "lineStyle"))

        strokeWidth = aDecoder.decodeObject(forKey: "strokeWidth") as! CGFloat
        if let lineJoin = CGLineJoin(rawValue: aDecoder.decodeInt32(forKey: "lineJoin")) {
            self.lineJoin = lineJoin
        }
        if aDecoder.containsValue(forKey: "padding") {
            padding = aDecoder.decodeUIEdgeInsets(forKey: "padding")
        }
        if aDecoder.containsValue(forKey: "margin") {
            padding = aDecoder.decodeUIEdgeInsets(forKey: "margin")
        }
        if aDecoder.containsValue(forKey: "cornerRadius") {
            cornerRadius = aDecoder.decodeObject(forKey: "cornerRadius") as? CGFloat
        }
        if let shadow = aDecoder.decodeObject(forKey: "shadow") as? NSShadow {
            self.shadow = shadow
        }
        if let fillColor = aDecoder.decodeObject(forKey: "fillColor") as? UIColor {
            self.fillColor = fillColor
        }
    }

    public func encode(with aCoder: NSCoder) {
        aCoder.encode(lineStyle, forKey: "lineStyle")
        aCoder.encode(strokeWidth, forKey: "strokeWidth")
        aCoder.encode(strokeColor, forKey: "strokeColor")
        aCoder.encode(lineJoin, forKey: "lineJoin")
        if let padding = padding {
            aCoder.encode(padding, forKey: "padding")
        }
        if let margin = margin {
            aCoder.encode(margin, forKey: "margin")
        }
        if let cornerRadius = cornerRadius {
            aCoder.encode(cornerRadius, forKey: "cornerRadius")
        }
        if let shadow = shadow {
            aCoder.encode(shadow, forKey: "shadow")
        }
        if let fillColor = fillColor {
            aCoder.encode(fillColor, forKey: "fillColor")
        }
    }

    public func copy(with zone: NSZone? = nil) -> Any {
        let textBorder = TextTag(strokeColor: strokeColor)
        textBorder.lineStyle = lineStyle
        textBorder.strokeWidth = strokeWidth
        textBorder.lineJoin = lineJoin
        textBorder.padding = padding
        textBorder.margin = margin
        textBorder.cornerRadius = cornerRadius
        textBorder.shadow = shadow
        textBorder.fillColor = fillColor
        return textBorder
    }

    /// TextAttachmentContainer
    public func drawAttachment(context: CGContext, textAttachmentBounds: CGRect, textContainer: NSTextContainer, characterIndex: Int) {
        guard let textStorage = textContainer.layoutManager?.textStorage else {
            return
        }

        var borderRect = textAttachmentBounds
        if let margin = margin {

            borderRect.origin.x -= margin.left
            borderRect.origin.y -= margin.top
            borderRect.size.width -= margin.right - margin.left
            borderRect.size.height -= margin.bottom - margin.top
        }

        var textRect = borderRect
        if let padding = padding {

            textRect.origin.x -= padding.left
            textRect.origin.y -= padding.top
            textRect.size.width -= padding.right - padding.left
            textRect.size.height -= padding.bottom - padding.top
        }

        let stringDrawingOptions: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.alignment = .center
        paragraphStyle.lineBreakMode = .byTruncatingTail

        if let attributedText = attributedText {

            if attributedText.attribute(.textAttachment, at: 0, effectiveRange: nil) != nil {

                let textLayoutManager = TextLayoutManager()
                let textStorage = NSTextStorage(attributedString: attributedText)
                textStorage.addLayoutManager(textLayoutManager)
                let tempTextContainer = NSTextContainer(size: textRect.size)
                tempTextContainer.lineFragmentPadding = 0
                textLayoutManager.addTextContainer(tempTextContainer)
                let glyphRange = textLayoutManager.glyphRange(for: tempTextContainer)

                drawBorder(textRect: textRect)

                textLayoutManager.drawBackground(forGlyphRange: glyphRange, at: textRect.origin)
                textLayoutManager.drawGlyphs(forGlyphRange: glyphRange, at: textRect.origin)
            } else {

                var rect = attributedText.boundingRect(with: textRect.size, options: stringDrawingOptions, context: nil)

                rect.origin.y += textRect.origin.y + (textRect.height - rect.height) / 2
                rect.origin.x = textRect.origin.x

                drawBorder(textRect: rect)

                let tempAttributedText = NSMutableAttributedString(attributedString: attributedText)
                tempAttributedText.addAttributes([.paragraphStyle: paragraphStyle], range: NSRange(location: 0, length: attributedText.length))
                tempAttributedText.draw(with: rect, options: stringDrawingOptions, context: nil)
            }
        } else {
            guard let font = textStorage.attribute(NSAttributedString.Key.font, at: characterIndex, effectiveRange: nil), let text = self.text else {
                return
            }

            var textAttributes = [NSAttributedString.Key.font: font, NSAttributedString.Key.paragraphStyle: paragraphStyle]
            if let textColor = textStorage.attribute(NSAttributedString.Key.foregroundColor, at: characterIndex, effectiveRange: nil) as? UIColor {

                textAttributes[NSAttributedString.Key.foregroundColor] = textColor
            }

            var rect = text.boundingRect(with: textRect.size,
                                         options: stringDrawingOptions,
                                         attributes: [NSAttributedString.Key.font: font],
                                         context: nil)

            rect.origin.y += textRect.origin.y + (textRect.height - rect.height) / 2
            rect.origin.x = textRect.origin.x

            drawBorder(textRect: rect)

            text.draw(in: rect, withAttributes: textAttributes)
        }
    }

    public func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {

        guard let textContainer = textContainer, let textStorage = textContainer.layoutManager?.textStorage else {
            return CGRect.zero
        }

        var textRect = CGRect.zero

        let tempLineFrag = CGRect(x: position.x, y: position.y, width: lineFrag.width, height: lineFrag.height)

        let stringDrawingOptions: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]

        if let attributedText = attributedText {

            if attributedText.attribute(.textAttachment, at: 0, effectiveRange: nil) != nil {
                let textLayoutManager = TextLayoutManager()
                let textStorage = NSTextStorage(attributedString: attributedText)
                textStorage.addLayoutManager(textLayoutManager)
                let tempTextContainer = NSTextContainer(size: tempLineFrag.size)
                tempTextContainer.lineFragmentPadding = 0
                textLayoutManager.addTextContainer(tempTextContainer)
                let glyphRange = textLayoutManager.glyphRange(for: tempTextContainer)
                textRect = textLayoutManager.boundingRect(forGlyphRange: glyphRange, in: tempTextContainer)
            } else {
                textRect = attributedText.boundingRect(with: tempLineFrag.size, options: stringDrawingOptions, context: nil)
            }
        } else if let font = textStorage.attribute(NSAttributedString.Key.font, at: charIndex, effectiveRange: nil), let text = self.text {
            textRect = text.boundingRect(with: tempLineFrag.size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedString.Key.font: font], context: nil)
        }

        if let padding = padding {
            textRect.size = CGSize(width: textRect.width + padding.right - padding.left, height: textRect.height + padding.bottom - padding.top)
        }

        if let margin = margin {
            textRect.size = CGSize(width: textRect.width + margin.right - margin.left, height: textRect.height + margin.bottom - margin.top)
        }

        textRect = textRect.offsetBy(dx: position.x, dy: (tempLineFrag.size.height - textRect.size.height) / 2)

        return textRect
    }

    public func layoutManager(_ layoutManager: NSLayoutManager, shouldSetLineFragmentRect lineFragmentRect: UnsafeMutablePointer<CGRect>, lineFragmentUsedRect: UnsafeMutablePointer<CGRect>, baselineOffset: UnsafeMutablePointer<CGFloat>, in textContainer: NSTextContainer, forGlyphRange glyphRange: NSRange) -> Bool {

        if let margin = margin {
            lineFragmentRect.pointee.size.height += margin.bottom - margin.top
            lineFragmentUsedRect.pointee.size.height += margin.bottom - margin.top
        }

        if let padding = padding {
            lineFragmentRect.pointee.size.height += padding.bottom - padding.top
            lineFragmentUsedRect.pointee.size.height += padding.bottom - padding.top
        }

        lineFragmentRect.pointee.size.height = ceil(lineFragmentRect.pointee.size.height)
        lineFragmentUsedRect.pointee.size.height = ceil(lineFragmentUsedRect.pointee.size.height)
        lineFragmentRect.pointee.size.width = ceil(lineFragmentRect.pointee.size.width)
        lineFragmentUsedRect.pointee.size.width = ceil(lineFragmentUsedRect.pointee.size.width)

        return true
    }

    public func layoutManager(_ layoutManager: NSLayoutManager, lineSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
        if #available(iOS 9.0, *) {
            return 0
        } else {
            var lineSpacing: CGFloat = 0

            if let margin = margin {

                lineSpacing += margin.bottom - margin.top
            }

            if let padding = padding {

                lineSpacing += padding.bottom - padding.top
            }

            return lineSpacing
        }
    }

    func drawBorder(textRect: CGRect) {
        var borderRect: CGRect = textRect
        if let padding = padding {

            borderRect.origin.x += padding.left
            borderRect.origin.y += padding.top
            borderRect.size.width += padding.right - padding.left
            borderRect.size.height += padding.bottom - padding.top
        }
        var bezierPath: UIBezierPath
        if let corner = corners {
            if let size = cornerRadii {
                bezierPath = UIBezierPath(roundedRect: borderRect, byRoundingCorners: corner, cornerRadii: size)
            } else {
                let width  = borderRect.width/2
                let height = borderRect.height/2
                let borderWidth = width < height ? width : height
                bezierPath = UIBezierPath(roundedRect: borderRect, byRoundingCorners: corner, cornerRadii: CGSize(width: borderWidth, height: borderWidth))
            }
        } else {
            bezierPath = cornerRadius == nil ? UIBezierPath(rect: borderRect) : UIBezierPath(roundedRect: borderRect, cornerRadius: CGFloat(cornerRadius!))
        }

        bezierPath.lineWidth = CGFloat(strokeWidth)
        bezierPath.lineJoinStyle = lineJoin
        fillColor?.setFill()
        bezierPath.fill()
        strokeColor.setStroke()
        bezierPath.stroke()
    }

}

public extension NSAttributedString {
    convenience init(textTag: TextTag) {
        self.init(textAttachment: textTag)
    }
}
